require 'sinatra/base'
require 'taps/config'
require 'taps/utils'
require 'taps/db_session'
require 'taps/data_stream'

module Taps
class Server < Sinatra::Base
  use Rack::Auth::Basic do |login, password|
    login == Taps::Config.login && password == Taps::Config.password
  end

  use Rack::Deflater unless ENV['NO_DEFLATE']

  set :raise_errors => false
  set :show_exceptions => false

  error do
    e = request.env['sinatra.error']
    puts "ERROR: #{e.class}: #{e.message}"
    begin
      require 'hoptoad_notifier'
      HoptoadNotifier.configure do |config|
        config.api_key = ENV["HOPTOAD_API_KEY"]
      end
      HoptoadNotifier.notify(e)
      puts "  notified Hoptoad"
    rescue LoadError
      puts "An error occurred but Hoptoad was not notified. To use Hoptoad, please"
      puts "install the 'hoptoad_notifier' gem and set ENV[\"HOPTOAD_API_KEY\"]"
    end
    if e.kind_of?(Taps::BaseError)
      content_type "application/json"
      halt 412, OkJson.encode({ 'error_class' => e.class.to_s, 'error_message' => e.message, 'error_backtrace' => e.backtrace.join("\n") })
    else
      "Taps Server Error: #{e}\n#{e.backtrace}"
    end
  end

  before do
    unless request.path_info == '/health'
      major, minor, patch = request.env['HTTP_TAPS_VERSION'].split('.') rescue []
      unless "#{major}.#{minor}" == Taps.compatible_version && patch.to_i >= 23
        halt 417, "Taps >= v#{Taps.compatible_version}.22 is required for this server"
      end
    end
  end

  get '/health' do
    content_type 'application/json'
    OkJson.encode({ :ok => true })
  end

  get '/' do
    "hello"
  end

  post '/sessions' do
    key = rand(9999999999).to_s

    if ENV['NO_DEFAULT_DATABASE_URL']
      database_url = request.body.string
    else
      database_url = Taps::Config.database_url || request.body.string
    end

    DbSession.create(:key => key, :database_url => database_url, :started_at => Time.now, :last_access => Time.now)

    "/sessions/#{key}"
  end

  post '/sessions/:key/push/verify_stream' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    state = DataStream.parse_json(params[:state])
    stream = nil

    size = 0
    session.conn do |db|
      Taps::Utils.server_error_handling do
        stream = DataStream.factory(db, state)
        stream.verify_stream
      end
    end

    content_type 'application/json'
    OkJson.encode({ :state => stream.to_hash })
  end

  post '/sessions/:key/push/table' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    json = DataStream.parse_json(params[:json])

    size = 0
    session.conn do |db|
      Taps::Utils.server_error_handling do
        stream = DataStream.factory(db, json[:state])
        size = stream.fetch_remote_in_server(params)
      end
    end

    # TODO: return the stream's state with the size
    size.to_s
  end

  post '/sessions/:key/push/reset_sequences' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    Taps::Utils.schema_bin(:reset_db_sequences, session.database_url)
  end

  post '/sessions/:key/push/schema' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    schema_data = request.body.read
    Taps::Utils.load_schema(session.database_url, schema_data)
  end

  post '/sessions/:key/push/indexes' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    index_data = request.body.read
    Taps::Utils.load_indexes(session.database_url, index_data)
  end

  post '/sessions/:key/pull/schema' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    Taps::Utils.schema_bin(:dump_table, session.database_url, params[:table_name])
  end

  get '/sessions/:key/pull/indexes' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    content_type 'application/json'
    Taps::Utils.schema_bin(:indexes_individual, session.database_url)
  end

  get '/sessions/:key/pull/table_names' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    tables = []
    session.conn do |db|
      tables = db.tables
    end

    content_type 'application/json'
    OkJson.encode(tables)
  end

  post '/sessions/:key/pull/table_count' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    count = 0
    session.conn do |db|
      count = db[ params[:table].to_sym.identifier ].count
    end
    count.to_s
  end

  post '/sessions/:key/pull/table' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    encoded_data = nil
    stream = nil

    session.conn do |db|
      state = OkJson.decode(params[:state]).symbolize_keys
      stream = Taps::DataStream.factory(db, state)
      encoded_data = stream.fetch.first
    end

    checksum = Taps::Utils.checksum(encoded_data).to_s
    json = OkJson.encode({ :checksum => checksum, :state => stream.to_hash })

    content, content_type_value = Taps::Multipart.create do |r|
      r.attach :name => :encoded_data,
        :payload => encoded_data,
        :content_type => 'application/octet-stream'
      r.attach :name => :json,
        :payload => json,
        :content_type => 'application/json'
    end

    content_type content_type_value
    content
  end

  delete '/sessions/:key' do
    session = DbSession.filter(:key => params[:key]).first
    halt 404 unless session

    session.destroy

    "ok"
  end

end
end
